5: Issuing a Credential
Now the Issuer issues credentials
Now that a connection has been made between the Issuer
and the Holder
, the Issuer
can send the credential to the
Holder
using the connection_id
from the Issuer's
perspective.
Again both tenants can listen for events on the topic:
credentials
Issuing a Non-Revocable Credential
To issue a non-revocable credential, the issuer needs to send a POST request to the appropriate endpoint. Below is an example of how to issue a non-revocable credential:
curl -X 'POST' \
'https://cloudapi.test.didxtech.com/tenant/v1/issuer/credentials' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'x-api-key: tenant.<Issuer token>' \
-d '{
"type": "indy",
"indy_credential_detail": {
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"attributes": {
"Name": "Alice",
"Surname": "Holder",
"Age": "25"
}
},
"connection_id": "c78f9423-370e-4800-a48e-962456083943",
"protocol_version": "v2"
}'
Response:
{
"attributes": {
"Name": "Alice",
"Surname": "Holder",
"Age": "25"
},
"connection_id": "c78f9423-370e-4800-a48e-962456083943",
"created_at": "2023-11-20T09:59:29.820002Z",
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"credential_exchange_id": "v2-f126edb7-1ac1-43a3-bf1f-60b8feae4701",
"did": null,
"error_msg": null,
"protocol_version": "v2",
"role": "issuer",
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0",
"state": "offer-sent",
"thread_id": "9ceeb941-4ebd-42ec-9ffc-ea0b7fe39722",
"type": "indy",
"updated_at": "2023-11-20T09:59:29.820002Z"
}
As you can see from the state, an offer has now been sent, and needs to be accepted/requested by the holder.
Note that the issuer will now have what's called a credential exchange record in state: offer-sent. Pending exchange records can be viewed by calling
GET /v1/issuer/credentials
, and completed credential exchange records are deleted by default, but can be preserved by adding an optionalsave_exchange_record=True
field to the request.
Issuing a Revocable Credential
Credentials that support revocation are issued in the same way as described above, but there is additional information that the issuer needs to keep track of.
There is a webhook event topic that an issuer can subscribe to called "issuer_cred_rev"
. The "issuer_cred_rev"
event has information on the issued credential and how it is connected to the revocation registries and some metadata.
This event will fire under two circumstances:
- Once a credential (with revocation support) is issued.
- And when a credential is revoked.
The state of the event will correspond with these events, i.e. first it will be "issued" and after revocation it will be "revoked".
Let's take a look at an example event:
{
"wallet_id": "5df42bab-6719-4c8a-a615-8086435d4de4",
"topic": "issuer_cred_rev",
"origin": "tenant faber",
"group_id": "GroupA",
"payload": {
"created_at": "2024-04-30T08:51:18.177543Z",
"cred_def_id": "QrMaE11MnC6zjKNY1pxbq8:3:CL:8:Epic",
"cred_ex_id": "af4bad3f-3fcc-47ab-85e6-24224dcb2779",
"cred_ex_version": "2",
"cred_rev_id": "2",
"record_id": "57bd9c72-fa29-4f65-bd89-4e241471073a",
"rev_reg_id": "QrMaE11MnC6zjKNY1pxbq8:4:QrMaE11MnC6zjKNY1pxbq8:3:CL:8:Epic:CL_ACCUM:53462552-d716-4b0b-8b5c-914a3574d2c4",
"state": "issued",
"updated_at": "2024-04-30T08:51:18.177543Z"
}
}
Taking a look at the payload, there are a few fields we are interested in:
"cred_def_id"
or credential definition id: The same id the issuer gets when creating a credential definition (also used when issuing credentials)."cred_ex_id"
or credential exchange id: This is the same exchange id that can be found in the credential exchange record, the protocol version is just stripped here."cred_rev_id"
or credential revocation id: This is the id that ties the credential to the revocation registry."rev_reg_id"
or revocation registry id: This is the id of the revocation registry the credential was issued against.
The "issuer_cred_rev"
event is not the only place this data is available. Under the issuer API,
there is an endpoint: GET /v1/issuer/credentials/revocation/record
.
This endpoint will return the payload object of the "issuer_cred_rev"
event:
{
"created_at": "2024-04-30T08:51:18.177543Z",
"cred_def_id": "QrMaE11MnC6zjKNY1pxbq8:3:CL:8:Epic",
"cred_ex_id": "af4bad3f-3fcc-47ab-85e6-24224dcb2779",
"cred_ex_version": "2",
"cred_rev_id": "2",
"record_id": "57bd9c72-fa29-4f65-bd89-4e241471073a",
"rev_reg_id": "QrMaE11MnC6zjKNY1pxbq8:4:QrMaE11MnC6zjKNY1pxbq8:3:CL:8:Epic:CL_ACCUM:53462552-d716-4b0b-8b5c-914a3574d2c4",
"state": "issued",
"updated_at": "2024-04-30T08:51:18.177543Z"
}
This endpoint has three query parameters:
"credential_exchange_id"
"credential_revocation_id"
"revocation_registry_id"
If "credential_exchange_id"
is not provided, both the "credential_revocation_id"
and "revocation_registry_id"
must be provided.
So with the "credential_exchange_id"
(from the credential exchange record), an issuer can get the relevant data
needed to revoke the credential associated with the exchange id.
[!NOTE] Issuers take note: The most important thing the issuer needs to keep track of is how their credential exchange ids map to credentials they have issued to holders. Without knowing how their exchange ids map to their holders, they won't know which credential to revoke.
Holder requests credential
Now the Holder
needs to respond to the credential sent to them. Below the Holder
is getting all their connections.
We are doing this to get the connection_id
of the connection to the issuer.
This connection_id
can also be gotten from the SSE events
.
curl -X 'GET' \
'https://cloudapi.test.didxtech.com/tenant/v1/connections' \
-H 'accept: application/json' \
-H 'x-api-key: tenant.<Holder token>'
Response:
[
{
"alias": "Holder <> Issuer",
"connection_id": "ac3b0d56-eb33-408a-baeb-0370164d47ae",
"connection_protocol": "connections/1.0",
"created_at": "2023-11-20T09:56:41.437966Z",
"error_msg": null,
"invitation_key": "91ZNSpDgVoV12kHcmUqyp1JmGeKE7oGi9NFd2WMzKt4X",
"invitation_mode": "once",
"invitation_msg_id": "6a86e6c7-af25-4e5d-87fe-b42f559b13b9",
"my_did": "MYhLew4uq58mou8SCTNFYp",
"state": "completed",
"their_did": "6wMwbinRJ5XKyBJKm7P5av",
"their_label": "Demo Issuer",
"their_public_did": null,
"their_role": "inviter",
"updated_at": "2023-11-20T09:56:41.656141Z"
}
]
Note the connection_id
in the above response.
The Holder
can then find the credentials offered to them on this connection_id
by calling /v1/issuer/credentials
with the optional connection_id
query parameter:
curl -X 'GET' \
'https://cloudapi.test.didxtech.com/tenant/v1/issuer/credentials?connection_id=ac3b0d56-eb33-408a-baeb-0370164d47ae' \
-H 'accept: application/json' \
-H 'x-api-key: tenant.<Holder token>'
Response:
[
{
"attributes": {
"Name": "Alice",
"Surname": "Holder",
"Age": "25"
},
"connection_id": "ac3b0d56-eb33-408a-baeb-0370164d47ae",
"created_at": "2023-11-20T09:59:29.868946Z",
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"credential_exchange_id": "v2-c492cec7-2f2d-4d5f-b839-b57dcd8f8eee",
"did": null,
"error_msg": null,
"protocol_version": "v2",
"role": "holder",
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0",
"state": "offer-received",
"thread_id": "9ceeb941-4ebd-42ec-9ffc-ea0b7fe39722",
"type": "indy",
"updated_at": "2023-11-20T09:59:29.868946Z"
}
]
Note the credential_exchange_id
and state: offer-received
. Additionally, note that the holder and the issuer have
different credential_exchange_id
references for the same credential exchange interaction.
The Holder
can now request the credential, using the credential_exchange_id
from the above response,
by calling /v1/issuer/credentials/{credential_exchange_id}/request
:
curl -X 'POST' \
'https://cloudapi.test.didxtech.com/tenant/v1/issuer/credentials/v2-c492cec7-2f2d-4d5f-b839-b57dcd8f8eee/request' \
-H 'accept: application/json' \
-H 'x-api-key: tenant.<Holder token>' \
-d ''
Response:
{
"attributes": {
"Name": "Alice",
"Surname": "Holder",
"Age": "25"
},
"connection_id": "ac3b0d56-eb33-408a-baeb-0370164d47ae",
"created_at": "2023-11-20T09:59:29.868946Z",
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"credential_exchange_id": "v2-c492cec7-2f2d-4d5f-b839-b57dcd8f8eee",
"did": null,
"error_msg": null,
"protocol_version": "v2",
"role": "holder",
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0",
"state": "request-sent",
"thread_id": "9ceeb941-4ebd-42ec-9ffc-ea0b7fe39722",
"type": "indy",
"updated_at": "2023-11-20T10:02:02.708045Z"
}
The holder request has been sent, and an automated workflow will transition the credential to being stored in the holder's wallet.
We can listen on SSE and wait for state
to be done
on the topic
: credentials
{
"wallet_id": "7bb24cc8-2e56-4326-9020-7870ad67b257",
"topic": "credentials",
"origin": "multitenant",
"payload": {
"attributes": null,
"connection_id": "ac3b0d56-eb33-408a-baeb-0370164d47ae",
"created_at": "2023-11-20T09:59:29.868946Z",
"credential_definition_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"credential_exchange_id": "v2-c492cec7-2f2d-4d5f-b839-b57dcd8f8eee",
"did": null,
"error_msg": null,
"protocol_version": "v2",
"role": "holder",
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0",
"state": "done",
"thread_id": "9ceeb941-4ebd-42ec-9ffc-ea0b7fe39722",
"type": "indy",
"updated_at": "2023-11-20T10:02:03.043100Z"
}
}
Once the state is done, the credential will be in the Holder
's wallet. We can list the credential in
the wallet by doing the following call as the Holder
:
curl -X 'GET' \
'https://cloudapi.test.didxtech.com/tenant/v1/wallet/credentials' \
-H 'accept: application/json' \
-H 'x-api-key: tenant.<Holder token>'
Response:
{
"results": [
{
"attrs": {
"Surname": "Holder",
"Name": "Alice",
"Age": "25"
},
"cred_def_id": "QrHj82kaE61jnB5451zvvG:3:CL:12:Demo Person",
"cred_rev_id": null,
"referent": "86dfb6ef-1ff5-41fd-977b-092a1d97e20b",
"rev_reg_id": null,
"schema_id": "FS9J6WZ6KVxwy5eGH32CgM:2:Person:0.1.0"
}
]
}
Note: the credential has no reference to a
credential_exchange_id
. In the wallet context, thereferent
is the credential id, and is different from thecredential_exchange_id
used during the credential exchange.
Hooray! 🥳🎉 The holder now has a credential!